﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using HelixToolkit.Wpf;

namespace GameBoard
{
    /// <summary>
    /// Interaction logic for GameBoardVisual.xaml
    /// </summary>
    public partial class GameBoardVisual : UserControl, IDisposable
    {
        private readonly VisualsViewModel _viewModel = VisualsViewModel.Instance;
        public BoardsViewModel.BoardTypes BoardType { get; private set; }
        private AdjustVisualsViewModel _adjustVisualsViewModel;
        public GameBoardVisual(BoardsViewModel.BoardTypes theType)
        {
            InitializeComponent();
            AddLights();
            _adjustVisualsViewModel = new AdjustVisualsViewModel(Viewport.Camera);
            Loaded += ZoomOnLoad;
            Viewport.Background = new SolidColorBrush(Colors.SkyBlue);
            Viewport.Camera.Position = new Point3D(1, 52, 5);
            Viewport.Camera.UpDirection = new Vector3D(0, 1, 1);
            Viewport.Camera.LookDirection = new Vector3D(0, 0, -1);
            Viewport.IsRotationEnabled = false;
            Viewport.PanGesture = new MouseGesture(MouseAction.LeftClick);
            BoardType = theType;
        }

        internal void AddLights()
        {
            Viewport.Children.Add(new DefaultLights());
            Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, 0, 1)));
            Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, 0, -1)));

            Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, 1, 0)));
            Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(0, -1, 0)));

            Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(-1, 0, 0)));
            Viewport.Lights.Children.Add(new DirectionalLight(Colors.White, new Vector3D(1, 0, 0)));
        }


        private bool multiSelect;

        internal void Viewport_MouseUp(object sender, MouseButtonEventArgs args)
        {
            if (args.ChangedButton != MouseButton.Left)
            {
                return;
            }
            Point mouseposition = args.GetPosition(Viewport);
            var testpoint3D = new Point3D(mouseposition.X, mouseposition.Y, 0);
            var testdirection = new Vector3D(mouseposition.X, mouseposition.Y, 10);
            var pointparams = new PointHitTestParameters(mouseposition);
            var rayparams = new RayHitTestParameters(testpoint3D, testdirection);

            if ((Keyboard.Modifiers & ModifierKeys.Control) > 0)
            {
                multiSelect = true;
            }
            else
            {
                multiSelect = false;
            }
            //test for a result in the Viewport3D
            VisualTreeHelper.HitTest(Viewport, null, HTResult, pointparams);
        }

        internal HitTestResultBehavior HTResult(HitTestResult rawresult)
        {
            //MessageBox.Show(rawresult.ToString());
            var rayResult = rawresult as RayHitTestResult;

            if (rayResult != null)
            {
                var rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;
                if (rayMeshResult != null)
                {
                    return HandleHit(rayMeshResult);
                }
            }
            return HitTestResultBehavior.Stop;
        }

        private HitTestResultBehavior HandleHit(RayMeshGeometry3DHitTestResult rayMeshResult)
        {
            var hitgeo = rayMeshResult.ModelHit as GeometryModel3D;
            if (_viewModel.TapeMeasurerActive)
            {
                Point3D foundPoint = new Point3D(0, 0, 0);
                rayMeshResult.VisualHit.Transform.TryTransform(rayMeshResult.PointHit, out foundPoint);
                _viewModel.HandleTapeMeasurerHit(foundPoint);
                return HitTestResultBehavior.Stop;
            }
            var hitTerrain = rayMeshResult.VisualHit as ITerrainFeature;
            bool isValidHit = false;
            if (hitTerrain != null)
            {
                if (hitTerrain.IsSolid)
                {
                    isValidHit = true;
                }
            }
            var belongsToMoveablePicture = CheckVisualBelongsToMoveablePicture(rayMeshResult.VisualHit);
            if (belongsToMoveablePicture)
            {
                isValidHit = true;
            }
            if (!isValidHit)
            {
                return HitTestResultBehavior.Continue;
            }
            HandleValidHit(rayMeshResult);
            return HitTestResultBehavior.Stop;
        }

        private bool CheckVisualBelongsToMoveablePicture(Visual3D visual3D)
        {
            var ivis = visual3D as IVisual;
            if (ivis != null)
            {
                if (ivis.BelongsToMoveablePicture)
                {
                    return true;
                }
            }
            return false;
        }


        private void HandleValidHit(RayMeshGeometry3DHitTestResult rayMeshResult)
        {
			Point3D foundPoint = new Point3D(0, 0, 0);
			rayMeshResult.VisualHit.Transform.TryTransform(rayMeshResult.PointHit, out foundPoint);
			if (_viewModel.TerrainViewModel.ShapePlacementType == TerrainViewModel.ShapeMode.None)
            {
                bool isSolidTerrain = false;
                var terrainHit = rayMeshResult.VisualHit as ITerrainFeature;
                if (terrainHit != null && terrainHit.IsSolid)
                {
                    isSolidTerrain = true;
                }
                if (!doubleClick && CheckVisualBelongsToMoveablePicture(rayMeshResult.VisualHit))
                {
                    if (!multiSelect)
                        _viewModel.ClearSelectedCharacters();
                    _viewModel.ToggleSelectCharacterFromVisual(rayMeshResult.VisualHit as IVisual);
                }
                //Prevents moving based on character visuals
                else if (doubleClick && isSolidTerrain)
                {
                    if (_viewModel.CharactersToMoveablePicture.Any(x => x.Value.IsSelected))
                    {
                        _viewModel.MoveSelectedPiecesTo(foundPoint);
                    }
                }
                //The hit was valid, but the user didn't move a piece onto solid terrain 
                //or select a piece click a piece. Do nothing.
            }
            else
            {
                DrawShape(foundPoint);
            }
        }

        private Cursor _currentMouseCursor = Cursors.Arrow;
        private readonly List<Point3D> _drawnPoints = new List<Point3D>();

        private void DrawShape(Point3D point3D)
        {
            _drawnPoints.Add(point3D);
            switch (_viewModel.TerrainViewModel.ShapePlacementType)
            {
                case TerrainViewModel.ShapeMode.Sphere:
                {
                    _viewModel.DrawSphere(point3D);
                    _drawnPoints.Clear();
                    break;
                }
                case TerrainViewModel.ShapeMode.Cone:
                {
                    if (_drawnPoints.Count == 2)
                    {
                        _viewModel.DrawCone(_drawnPoints.First(), point3D);
                        _drawnPoints.Clear();
                    }
                    break;
                }
                case TerrainViewModel.ShapeMode.Line:
                {
                    if (_drawnPoints.Count == 2)
                    {
                        _viewModel.DrawLine(_drawnPoints.First(), point3D);
                        _drawnPoints.Clear();
                    }
                    break;
                }
                case TerrainViewModel.ShapeMode.Square:
                {
                    if (_drawnPoints.Count == 2)
                    {
                        _viewModel.DrawCube(_drawnPoints.First(), point3D);
                        _drawnPoints.Clear();
                    }
                    break;
                }
                case TerrainViewModel.ShapeMode.ObjectFile:
                {
                    if (_drawnPoints.Count == 1)
                    {
                        _viewModel.DrawObjectFile(point3D);
                        _drawnPoints.Clear();
                    }
                    break;
                }
                default:
                    throw new Exception("Unknown shape");
            }
        }

        internal void SetMouseCursorToAoEFor(TerrainViewModel.ShapeMode shapeMode)
        {
            if (shapeMode == TerrainViewModel.ShapeMode.None)
            {
                _currentMouseCursor = Cursors.Arrow;
            }
            else
            {
                _currentMouseCursor = Cursors.ScrollAll;
            }
            SetMouseCursorToType(_currentMouseCursor);
        }

        private bool doubleClick;

        private void Viewport_DoubleClick(object sender, MouseButtonEventArgs e)
        {
            doubleClick = true;
            Viewport_MouseUp(sender, e);
            doubleClick = false;
        }

        private readonly Dictionary<Guid, ModelVisual3D> _placedVisuals = new Dictionary<Guid, ModelVisual3D>();

        public void RemoveVisual(Guid toRemoveID)
        {
            if (_placedVisuals.ContainsKey(toRemoveID))
            {
                var match = _placedVisuals[toRemoveID];
                if (match is IFlip)
                {
                    _adjustVisualsViewModel.DeregisterFlipVisual(match as IFlip);
                }
                Viewport.Children.Remove(match);
                _placedVisuals.Remove(toRemoveID);
            }
        }

        public void AddVisual(Guid visualGroupID, ModelVisual3D toAdd)
        {
            if (toAdd == null)
                return;
            _placedVisuals.Add(visualGroupID, toAdd);
            Viewport.Children.Add(toAdd);
            if (toAdd is IFlip)
            {
                _adjustVisualsViewModel.RegisterFlipVisual(toAdd as IFlip);
            }
        }

        private readonly Stack<Rect3D> _lastZoom = new Stack<Rect3D>();
        private BoardsViewModel.BoardTypes boardTypes;

        internal void ZoomTo(Rect3D rect3D, int zoomSpeed)
        {
            //When the board isn't loaded, zooming using helix had an odd unfixable crash.
            //this dodges that.
            if (ActualHeight != 0 && ActualWidth != 0)
            {
                Viewport.ZoomExtents(rect3D, zoomSpeed);
            }
            else
            {
                _lastZoom.Push(rect3D);
            }
        }

        private void ZoomOnLoad(object sender, RoutedEventArgs e)
        {
            if (_lastZoom.Count > 0)
            {
                ZoomTo(_lastZoom.Pop(), 0);
                _lastZoom.Clear();
            }
        }

        internal void MatchOtherCamera(ProjectionCamera otherCamera)
        {
            Viewport.Camera.LookDirection = otherCamera.LookDirection;
            Viewport.Camera.Position = otherCamera.Position;
            Viewport.Camera.UpDirection = otherCamera.UpDirection;
        }

        internal ProjectionCamera Camera
        {
            get { return Viewport.Camera; }
        }


        public bool LockRotation
        {
            get { return Viewport.IsRotationEnabled; }
            set { Viewport.IsRotationEnabled = !value; }
        }

        private void Viewport_MouseEnter(object sender, MouseEventArgs e)
        {
            SetMouseCursorToType(_currentMouseCursor);
        }

        private void SetMouseCursorToType(Cursor _currentMouseCursor)
        {
            Mouse.OverrideCursor = _currentMouseCursor;
        }

        private void Viewport_MouseLeave(object sender, MouseEventArgs e)
        {
            Mouse.OverrideCursor = Cursors.Arrow;
        }


        internal void MoveVisual(Guid guid, Point3D dest)
        {
            if (!_placedVisuals.ContainsKey(guid))
            {
                return;
            }
            var matchingVisual = _placedVisuals[guid];
            var moveVis = matchingVisual as IMoveable;
            if (moveVis == null)
                return;
            moveVis.MoveTo(dest);
        }

        internal void ActivateVisuals(Guid guid)
        {
            if (!_placedVisuals.ContainsKey(guid))
            {
                return;
            }
            (_placedVisuals[guid] as IActivatable).Activate();
        }

        internal void DeactivateVisuals(Guid guid)
        {
            if (!_placedVisuals.ContainsKey(guid))
            {
                return;
            }
            (_placedVisuals[guid] as IActivatable).Deactivate();
        }

        public void Dispose()
        {
        }
    }
}